package zzxkj.blog.service.Impl;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.modelmapper.ModelMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import zzxkj.blog.common.lang.Consts;
import zzxkj.blog.dao.BlogDao;
import zzxkj.blog.dto.*;
import zzxkj.blog.pojo.Blog;
import zzxkj.blog.pojo.Tag;
import zzxkj.blog.service.BlogService;
import zzxkj.blog.utils.RedisUtil;

import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Service
public class BlogServiceImpl implements BlogService {
    private static final String BLOG_KEY_PREFIX = "rank:blog:";
    private static final String DAY_RANK_KEY_PREFIX = "rank:day:";
    private static final String ALL_RANK_KEY = "rank:all";
    private static final String WEEK_RANK_KEY = "rank:week";
    private final BlogDao blogDao;
    private final RedisUtil redisUtil;
    private final RedisTemplate redisTemplate;
    private final ModelMapper modelMapper;

    public BlogServiceImpl(BlogDao blogDao, RedisUtil redisUtil, RedisTemplate redisTemplate, ModelMapper modelMapper) {
        this.blogDao = blogDao;
        this.redisUtil = redisUtil;
        this.redisTemplate = redisTemplate;
        this.modelMapper = modelMapper;
    }

    /**
     * MM-dd
     * @return
     */
    public String getTodayTime(){
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd");
        String Today = simpleDateFormat.format(date);
        return Today;
    }
    @Override
    public String getTodayTimeKey(String key){
        Date date = new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd");
        String Today = simpleDateFormat.format(date);
        return (key + Today);
    }

    @Override
    public ShowBlog getBlogById(Long id) {
        return blogDao.getBlogById(id);
    }

    @Override
    public List<BlogQuery> getAllBlog(AdminSearchParam param) {
        List<BlogQuery> allBlogQuery = blogDao.getAllBlogQuery(param);
        return allBlogQuery;
    }

    @Override
    public int saveBlog(Blog blog) {
        blog.setCreateTime(new Date());
        blog.setUpdateTime(new Date());
        blog.setViews(0);
        //将标签的数据存到t_blogs_tag表中
        List<Tag> tags = blog.getTags();
        log.info("blog的tags={}",tags);
        //得到主键值
        Long blogId = Long.valueOf(blogDao.saveBlog(blog));
        BlogAndTag blogAndTag = new BlogAndTag();
        blogAndTag.setBlogId(blogId);
        //blogAndTag.setTagId(blog.getTags());
        for (Tag tag : tags) {
            blogAndTag = new BlogAndTag(tag.getId(),blog.getId());
            log.info("blogAndTag={}",blogAndTag);
            blogDao.saveBlogAndTag(blogAndTag);
        }
        return blogId==null?0:1;
    }

    @Override
    public int updateBlog(Blog blog) {
        blog.setUpdateTime(new Date());
        return blogDao.updateBlog(blog);
    }

    @Override
    public int deleteBlog(Long id) {
        blogDao.deleteBlogAndTag(id);
        blogDao.deleteBlog(id);
        return 1;
    }

    @Override
    public FirstPageBlog getOneFirstBlog(Long blogId) {
        return blogDao.getOneFirstBlog(blogId);
    }

    @Override
    public List<FirstPageBlog> getAllFirstPageBlog() {
        List<FirstPageBlog> result = blogDao.getFirstPageBlog();
        for (FirstPageBlog blog : result){
            String key = BLOG_KEY_PREFIX + blog.getId();
            if(redisUtil.hasKey(key)){
                Integer views = (Integer) redisUtil.hget(key,"views");
                blog.setViews(views);
            }
        }
        return result;
    }

    @Override
    public List<FirstPageBlog> getSearchBlog(String query) {
        return blogDao.getSearchBlog(query);
    }

    @Override
    public List<FirstPageBlog> getByTypeId(Long typeId) {
        return blogDao.getByTypeId(typeId);
    }

    @Override
    public List<FirstPageBlog> getByTagId(Long tagId) {
        return blogDao.getByTagId(tagId);
    }

    @Override
    public Map<String, List<DetailedBlog>> archiveBlog() {
        List<String> years = blogDao.findGroupYear();
        Map<String, List<DetailedBlog>> map = new LinkedHashMap<>();
        for (String year : years) {
            System.out.println(year);
            map.put(year, blogDao.findByYear(year));
        }
        return map;
    }

    @Override
    public Long countBlog() {
        return blogDao.countBlog();
    }

    @Override
    public List<RecommendBlog> listRecommendBlogTop(Integer size) {
        return blogDao.listRecommendBlogTop(size);
    }

    /**
     * 重启时初始化
     * 本周热议门文章
     */
    @Override
    public void initWeekRank() {
        // 获取7天的发表的文章
        // 使用hutool获取日期偏移，最终调用的还是Calendar
        DateTime time =  DateUtil.offsetDay(new Date(), -7);
        List<BlogRankInfo> blogLists = blogDao.get7Days(time);
        for (BlogRankInfo blog : blogLists){
            //按天数设置zset的key
            String key = DAY_RANK_KEY_PREFIX + DateUtil.format(blog.getCreateTime(), DatePattern.PURE_DATE_FORMAT);
            //添加到天数的有序列表
            redisUtil.zSet(key, blog.getId(), blog.getViews());
            //7天后自动过期(17号发表,7-(18 - 17)  = 6
            //创建时间距当前时间的差值
            long between = DateUtil.between(new Date(), blog.getCreateTime(), DateUnit.DAY);
            //有效时间
            long expireTime = (7-between)*24*60*60;
            //设置有效时间
            redisUtil.expire(key, expireTime);
            //缓存文章的一些基本信息
            CacheBlogForRank(blog, expireTime);
        }
        //做7天的并集列表
        zunionStore7DayForWeekRank();
    }

    /**
     * 周浏览量数据初始化，从数据库加载到内存
     * expire:一周
     */
    @Override
    public void initCacheChartData(){
        // HAS KEY Delete
        if(redisUtil.hasKey(Consts.CHART_VIEW_DATA_KEY)){
            redisUtil.del(Consts.CHART_VIEW_DATA_KEY);
        }
        //CHART_VIEW_DATA_KEY
        List<Object> viewList = blogDao.getDayViewsOf7Days();
        if(viewList == null){
            return;
        }
        redisUtil.lSet(Consts.CHART_VIEW_DATA_KEY, viewList, 24*3600);
    }
    /**
     * 缓存文章的一些基本信息，用于排行榜
     * */
    private void CacheBlogForRank(BlogRankInfo blog, Long expireTime) {
         String key = BLOG_KEY_PREFIX + blog.getId();
         if (!redisUtil.hasKey(key)){
             redisUtil.hset(key,"id", blog.getId(), expireTime);
             redisUtil.hset(key,"title", blog.getTitle(), expireTime);
             redisUtil.hset(key,"views", blog.getViews(), expireTime);
         }
    }

    /**
     * 通用，缓存文章的一些基本信息
     * @param blog
     * @param expireTime
     */
//    private void CacheBlogInfo(BlogRankInfo blog, Long expireTime){
//        String key = "blog:" + blog.getId();
//        boolean hasKey = redisUtil.hasKey(key);
//        if(!hasKey){
//            redisUtil.hset(key, "id", blog.getId(), expireTime);
//            redisUtil.hset(key,"title", blog.getTitle(), expireTime);
//            redisUtil.hset(key,"views", blog.getViews(), expireTime);
//        }
//    }

    /**
     * 根据博客创建时间，设置天排行榜
     * @param blogId
     * @param blogCreateTime
     * @param views
     */
   private void setDayRankOfBlogCreateTime(Long blogId ,Date blogCreateTime, Integer views){
       String day_key = DAY_RANK_KEY_PREFIX +DateUtil.format(blogCreateTime, DatePattern.PURE_DATE_FORMAT);
       log.info("day_key:{}", day_key);
       redisUtil.zSet(day_key, blogId, views);
   }
    /**
     * 合并每日为周（非自然周，只是指7天）
     * 按照views排序
     * */
    private void zunionStore7DayForWeekRank() {
        //今天的
        String currentKey = DAY_RANK_KEY_PREFIX + DateUtil.format(new Date(), DatePattern.PURE_DATE_FORMAT);
        //过去6天的
        List<String> otherKeys = new ArrayList<>();
        for (int i = -6; i < 0; i++){
            String temp = DAY_RANK_KEY_PREFIX + DateUtil.format(DateUtil.offsetDay(new Date(),i), DatePattern.PURE_DATE_FORMAT);
            otherKeys.add(temp);
        }
        redisUtil.zUnionAndStore(currentKey, otherKeys, WEEK_RANK_KEY);
    }
    /**
     * 总排行榜
     * 按照views排序
     * */
    private void redoAllRank() {
        String prevKey = BLOG_KEY_PREFIX + "*";
        Set<String> keys = redisTemplate.keys(prevKey);
        log.info("blog keys:{}", keys.toString());
        Set<ZSetOperations.TypedTuple> typedTupleSet = new HashSet<>();
        for (String key : keys){
            Map<Object, Object> info = new HashMap<>();
            info.put("id", redisUtil.hget(key, "id"));
            info.put("title", redisUtil.hget(key, "title"));
            String number = JSONUtil.toJsonStr(info);
            //Object bean = BeanUtil.mapToBean(info, Object.class, false);
            //new DefaultTypedTuple(key.split(":")[2]
            typedTupleSet.add(new DefaultTypedTuple(number, Double.valueOf(redisUtil.hget(key, "views").toString())));
        }
        redisUtil.batchZSet(ALL_RANK_KEY, typedTupleSet);
    }
    /**
     * 重置周排行榜
     * @param blog
     */
    private void redoWeekRank(DetailedBlog blog){
        if(!isOffset7Days(DateUtil.date(blog.getCreateTime()))){
            log.info("blog_{} is not in 7 days", blog.getId());
            return;
        }
        //设置日排行，方便后面的周排行
        this.setDayRankOfBlogCreateTime(blog.getId(), blog.getCreateTime(), blog.getViews());
        //重新做周排行榜集合
        this.zunionStore7DayForWeekRank();
    }

    /**
     * 同步到缓存里面,最后通过定时器定期存到我们的数据库中
     * @param blog
     */
    private void addViews(DetailedBlog blog){
        String key = BLOG_KEY_PREFIX + blog.getId();
        /**
         *如果Redis没有缓存该文章，就先从DB里面获取再加1
         * */
        if (!redisUtil.hasKey(key)){
            DetailedBlog DBblog = blogDao.getDetailedBlog(blog.getId());
            blog.setViews(DBblog.getViews() + 1);
            BlogRankInfo blogInfo = new BlogRankInfo();
            BeanUtils.copyProperties(blog, blogInfo);
            this.CacheBlogForRank(blogInfo, 7*24*3600L);
            log.info("blog info:{}", blogInfo.toString());
        }else{
            Integer views = (Integer) redisUtil.hget(key,"views");
            //存在则直接set实体类&Redis
            blog.setViews(views + 1);
            redisUtil.hset(key,"views", blog.getViews());
        }
        //天浏览量+1，这里直接对list中数据进行修改
        //redisUtils.incr(getTodayTimeKey(Consts.DAY_VIEW_DATA_KEY_PREFIX), 1);
        if(!redisUtil.hasKey(Consts.CHART_VIEW_DATA_KEY)) {
            this.initCacheChartData();
        }else{
            //如果没意外队尾数据就是今天的数据
            Object dayViewsJson = redisUtil.lGetIndex(Consts.CHART_VIEW_DATA_KEY, -1);
            //以防万一做个判断
            JSONObject dayViews = JSONUtil.parseObj(dayViewsJson);
            //如果不是当天数据，重新初始化
            if(!this.getTodayTime().equals(dayViews.getStr("dayTime"))){
                initCacheChartData();
                dayViewsJson = redisUtil.lGetIndex(Consts.CHART_VIEW_DATA_KEY, -1);
                dayViews = JSONUtil.parseObj(dayViewsJson);
            }
            log.info("before dayViews:{}", dayViews.toString());
            dayViews.put("views", dayViews.getInt("views") + 1);
            log.info("after dayViews:{}", dayViews.toString());
            redisUtil.lUpdateIndex(Consts.CHART_VIEW_DATA_KEY, -1, dayViews);
        }
    }

    /**
     * 判断是否是7天内新建的blog
     * @param dateTime
     * @return
     */
    private Boolean isOffset7Days(DateTime dateTime){
        // 使用hutool获取日期偏移，最终调用的还是Calendar
        DateTime end_time = new DateTime();
        DateTime start_time =  DateUtil.offsetDay(end_time, -7);
        return dateTime.isIn(start_time, end_time);
    }

    /**
     * 获取博客详情&增加浏览次数
    * */
    @Override
    public DetailedBlog getBlogDetail(Long id) {
        DetailedBlog blog = null;
        //该缓存不对view做统计
        String key = "blog:" + id;
        if(!redisUtil.hasKey(key)){
            blog = blogDao.getDetailedBlog(id);
            redisUtil.set(key ,JSONUtil.toJsonStr(blog), 3600*24);
        }else{
            blog = new DetailedBlog();
            Object jsonObj = JSONUtil.parseObj(redisUtil.get(key));
            modelMapper.map(jsonObj, blog);
            log.info("log:{}", blog.toString());
        }
        log.info("getBlogDetail");
        if (blog == null) {
            return null;
        }
        //新增浏览量
        this.addViews(blog);
        //重做周排行榜
        this.redoWeekRank(blog);
        //重做总排行榜
        this.redoAllRank();
        return blog;
    }

    /**
     * 获取当天浏览量
     * @return
     */
    @Override
    public Integer getNowDayViews(){
        return blogDao.getNowDayViews();
    }

    @Override
    public Integer dayViewInit(){
        return blogDao.dayViewInit();
    }

    @Override
    public Integer updateDayViews(Integer nowViews) {
        return blogDao.updateDayViews(nowViews);
    }
}
